본문으로 건너뛰기

useImperativeHandle

🧑🏻‍💻 useImperativeHandle


useImperativeHandle은 ref로 노출되는 핸들을 사용자가 직접 정의할 수 있게 해주는 React 훅이다.

( imperative : 명령적인, 피할 수 없는, 반드시 해야 하는, 필수의 )

✅ useImperativeHandle 문법

useImperativeHandle(ref, createHandle, dependencies?)
refforwardRef 렌더 함수에서 두 번째 인자로 받은 ref
createHandle인자가 없고 노출하려는 ref 핸들을 반환하는 함수
  • dependenciescreateHandle 코드 내에서 참조하는 모든 반응형 값을 나열한 목록이다. 리렌더링으로 인해 일부 의존성이 변경되거나 이 인수를 생략한 경우, createHandle 함수가 다시 실행되고 새로 생성된 핸들이 ref에 할당된다.
  • useImperativeHandleundefined를 반환한다. (return값 없음)

🧑🏻‍💻 알고 가기


✅ 주의 사항

  • 컴포넌트의 최상위 레벨에서 호출 가능, 또한 커스텀 훅에서 호출 가능하다. (React 훅)
  • prop으로 표현할 수 있는 것은 ref를 사용하지 않는다.
    • 예를 들어, Modal 컴포넌트에서 {open, close}와 같은 명령형 핸들로 노출하는 대신 <Modal isOpen={isOpen} />과 같은 isOpen prop을 권장한다. 이로써 코드가 더 간결하고 예측 가능하며, 컴포넌트의 독립성과 재사용성이 향상된다.
    • props로 표현할 수 없는 것에 대한 예로는 특정 노드로 스크롤하기, 노드에 초점 맞추기, 애니메이션 촉발하기, 텍스트 선택하기 등이 있다.

🧑🏻‍💻 활용 및 생각할 거리


✅ useImperativeHandle와 forwardRef

  • forwardRef와 함께 사용할 때

    부모 컴포넌트에서 자식 컴포넌트의 레퍼런스를 획득하고 useImperativeHandle을 사용하여 레퍼런스에 노출할 메서드나 데이터를 정의한다. 이는 부모 컴포넌트에서 자식 컴포넌트를 직접 조작해야 하는 경우에 유용하다.

  • forwardRef 없이 사용할 때

    컴포넌트 내부에서 특정 명령형 동작을 외부로 노출해야 하는 경우에 forwardRef 없이 가능하다.

  • 코드로 살펴보기
    import { forwardRef, useRef, useImperativeHandle } from 'react';

    const MyInput = forwardRef(function MyInput(props, ref) {
    const inputRef = useRef(null);

    useImperativeHandle(ref, () => {
    return {
    focus() {
    inputRef.current.focus();
    },
    };
    }, []);

    return <input {...props} ref={inputRef} />;
    });
    • forwarRef만 사용하면 부모 컴포넌트는 전체 <input> DOM 노드를 ref로 전달받게 된다.

    • 만약 <input> DOM 노드 전체를 ref로 전달하는 대신 DOM 노드에 대한 사용자 지정 값만 부모에게 전달하고 싶다면, 위의 코드와 같이 useImperativeHandle를 사용하여 한 메서드만을 전달할 수 있다.

      import { useRef } from 'react';
      import MyInput from './MyInput.js';

      export default function Form() {
      const ref = useRef(null);

      function handleClick() {
      ref.current.focus();
      }

      return (
      <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
      Edit
      </button>
      </form>
      );
      }
    • 이제 부모 컴포넌트가 MyInput에 대한 ref를 가져오면 focus 메서드를 호출할 수 있다. 그러나 기본 <input> DOM 노드의 전체 액세스 권한은 없다.

✅ useImperativeHandle를 왜 사용할까요?

객체 지향 프로그래밍에서는 캡슐화랑 은닉화가 중요하다. 비슷한 역할을 하는 속성(데이터)과 메소드(행동)들을 하나의 틀에 담는 것이다

  • ex) 캡슐화 : 라면 끓이기 예시
    • 가스레인지 : 가열의 역할
    • 물 넣기 : 라면의 물을 맞추는 역할
    • 재료 넣기 : 라면의 재료를 넣는 역할

이런 캡슐화는 기본적으로는 바람직하지만 button이나 input 요소 같이 재 사용성이 높은 말단 요소(가장 끝에서 작동하는 요소)에서는 불편할 수 있다.

이런 경우 DOM 요소에 접근하기 위해 사용되었던 ref 같은 경우 캡슐화로 인한 불편한 예시가 될 수 있는데, 이럴 경우 우리는 forwardRef를 사용해서 props로 ref를 내려줄 수 있다. 그러나 이런 경우 ref를 통한 DOM 조작의 전 권을 다 주게 된다.

필요한 기능 외로 건드리면 문제가 발생할 수도 있다…😢

그렇기 때문에 이럴 경우 useImperativeHadle을 사용한다. 원하는 DOM 조작 요소, 예를 들면 focus(), scroll() 같은 조작 요소들만 사용할 수 있게 허락할 수 있다!

✅ useImperativeHandle 사용 예시 코드

// App.js 부모 컴포넌트
import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
const ref = useRef(null);

function handleClick() {
ref.current.focus();
// This won't work because the DOM node isn't exposed:
// 이 작업은 DOM 노드가 노출되지 않으므로 작동하지 않습니다.
// ref.current.style.opacity = 0.5;
}

return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}

// MyInput.js 자식 컴포넌트
import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);

useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);

return <input {...props} ref={inputRef} />;
});

export default MyInput;

위에 코드와 같이 forwardRef로 전달 받은 ref 를 인수로 받으며 사용할 DOM 요소들만 정의해 준다. 부모 컴포넌트에서는 자식 컴포넌트에서 useImperativeHandle로 정의해 준 DOM 요소만 사용 가능하다.